// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include "QvisExtrudeStackedWindow.h"

#include <ExtrudeStackedAttributes.h>

#include <QButtonGroup>
#include <QCheckBox>
#include <QGroupBox>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QNarrowLineEdit.h>
#include <QPushButton>
#include <QRadioButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QvisVariableButton.h>

#define REPLICATE_EXTRUDE_OPERATOR

// Largely adapted from the original extrude operator.

// ****************************************************************************
// Method: QvisExtrudeStackedWindow::QvisExtrudeStackedWindow
//
// Purpose:
//   Constructor
//
// Note:       Autogenerated by xml2window.
//
// Programmer: xml2window
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

QvisExtrudeStackedWindow::QvisExtrudeStackedWindow(const int type,
                                                   ExtrudeStackedAttributes *subj,
                                                   const QString &caption,
                                                   const QString &shortName,
                                                   QvisNotepadArea *notepad)
: QvisOperatorWindow(type,subj, caption, shortName, notepad)
{
    atts = subj;
}


// ****************************************************************************
// Method: QvisExtrudeStackedWindow::~QvisExtrudeStackedWindow
//
// Purpose:
//   Destructor
//
// Note:       Autogenerated by xml2window.
//
// Programmer: xml2window
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

QvisExtrudeStackedWindow::~QvisExtrudeStackedWindow()
{
}


// ****************************************************************************
// Method: QvisExtrudeStackedWindow::CreateWindowContents
//
// Purpose:
//   Creates the widgets for the window.
//
// Note:       Autogenerated by xml2window.
//
// Programmer: xml2window
// Creation:   omitted
//
// Modifications:
//   Kathleen Biagas, Mon Oct 16 14:05:19 PDT 2023
//   Qt6 support: setMargin->setContentsMargins.
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::CreateWindowContents()
{
    QGridLayout *mainLayout = new QGridLayout(0);
    topLayout->addLayout(mainLayout);

    axisLabel = new QLabel(tr("Extrusion axis"), central);
    mainLayout->addWidget(axisLabel, 0,0);
    axis = new QLineEdit(central);
    connect(axis, SIGNAL(returnPressed()),
            this, SLOT(axisProcessText()));
    mainLayout->addWidget(axis, 0,1);

    // Variable to extrude
    variableGroup = new QGroupBox(tr("Extrude by variable"), central);
#if defined(REPLICATE_EXTRUDE_OPERATOR)
    variableGroup->setCheckable( true );
#endif
    variableGroup->setChecked( true );
    connect(variableGroup, SIGNAL(toggled(bool)),
            this, SLOT(variableGroupChanged(bool)));
    mainLayout->addWidget(variableGroup, 1,0, 8,4);
    QGridLayout *variableLayout = new QGridLayout(variableGroup);

    // Axes tree list
    variableTree = new QTreeWidget(variableGroup);
    variableTree->setSortingEnabled(false);
    variableTree->setRootIsDecorated(false);

    QTreeWidgetItem *header = new QTreeWidgetItem();
    header->setText(0,tr("Index"));
    header->setText(1,tr("Variable"));
    header->setText(2,tr("Min"));
    header->setText(3,tr("Max"));
    header->setText(4,tr("Scale"));
    variableTree->setHeaderItem(header);

    variableLayout->addWidget(variableTree, 0,0, 4,4);
    connect(variableTree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
            this, SLOT(variableSelected(QTreeWidgetItem*)));

    // Variable new/delete/up/down buttons
    int variableMask = QvisVariableButton::Scalars;
    variableAddButton = new QvisVariableButton(false, true, true,
                                               variableMask,
                                               variableGroup);
    variableAddButton->setText(tr("Add variable"));
    variableAddButton->setChangeTextOnVariableChange(false);
    variableLayout->addWidget(variableAddButton, 0, 4);
    connect(variableAddButton, SIGNAL(activated(const QString &)),
            this, SLOT(addVariable(const QString &)));

    variableDeleteButton = new QPushButton(tr("Delete"), variableGroup);
    variableLayout->addWidget(variableDeleteButton, 1, 4);
    connect(variableDeleteButton, SIGNAL(clicked()),
            this, SLOT(deleteVariable()));

    variableUpButton = new QPushButton(tr("Move up"), variableGroup);
    variableLayout->addWidget(variableUpButton, 2, 4);
    connect(variableUpButton, SIGNAL(clicked()),
            this, SLOT(moveVariableUp()));

    variableDownButton = new QPushButton(tr("Move down"), variableGroup);
    variableLayout->addWidget(variableDownButton, 3, 4);
    connect(variableDownButton, SIGNAL(clicked()),
            this, SLOT(moveVariableDown()));

    // Variable min/max/scale values
    variableMinValLabel = new QLabel(tr("Min value"),variableGroup);
    variableLayout->addWidget(variableMinValLabel, 4,0);

    variableMinVal = new QNarrowLineEdit(variableGroup);
    variableLayout->addWidget(variableMinVal, 4,1);
    connect(variableMinVal, SIGNAL(textChanged(const QString&)),
            this, SLOT(variableMinValueChanged(const QString&)));
    connect(variableMinVal, SIGNAL(returnPressed()),
            this, SLOT(variableValueProcessText()));

    variableMaxValLabel = new QLabel(tr("Max value"),variableGroup);
    variableLayout->addWidget(variableMaxValLabel, 4,2);

    variableMaxVal = new QNarrowLineEdit(variableGroup);
    variableLayout->addWidget(variableMaxVal, 4,3);
    connect(variableMaxVal, SIGNAL(textChanged(const QString&)),
            this, SLOT(variableMaxValueChanged(const QString&)));
    connect(variableMaxVal, SIGNAL(returnPressed()),
            this, SLOT(variableValueProcessText()));

    variableScaleValLabel = new QLabel(tr("Scale"),variableGroup);
    variableLayout->addWidget(variableScaleValLabel, 5,0);

    variableScaleVal = new QNarrowLineEdit(variableGroup);
    variableLayout->addWidget(variableScaleVal, 5,1);
    connect(variableScaleVal, SIGNAL(textChanged(const QString&)),
            this, SLOT(variableScaleValueChanged(const QString&)));
    connect(variableScaleVal, SIGNAL(returnPressed()),
            this, SLOT(variableValueProcessText()));

    variableMinValLabel->setEnabled(false);
    variableMinVal->setEnabled(false);
    variableMaxValLabel->setEnabled(false);
    variableMaxVal->setEnabled(false);
    variableScaleValLabel->setEnabled(false);
    variableScaleVal->setEnabled(false);

    variableResetExtentsButton =
      new QPushButton(tr("Reset all"), variableGroup);
    variableLayout->addWidget(variableResetExtentsButton, 5,2, 1,2);
    connect(variableResetExtentsButton, SIGNAL(clicked()),
            this, SLOT(resetVariableExtents()));


    variableDisplayGroup = new QGroupBox(variableGroup);
    variableDisplayGroup->setTitle(tr("Display by"));
    variableLayout->addWidget(variableDisplayGroup, 4,4, 2,1);
    QGridLayout *variableDisplayLayout = new QGridLayout(variableDisplayGroup);
    variableDisplayLayout->setContentsMargins(5,5,5,5);
    variableDisplayLayout->setSpacing(10);

    variableDisplay = new QWidget(central);
    variableDisplayButtonGroup= new QButtonGroup(variableDisplay);

    QVBoxLayout *variableDisplayTypeLayout = new QVBoxLayout(variableDisplay);
    variableDisplayTypeLayout->setContentsMargins(0,0,0,0);
    variableDisplayTypeLayout->setSpacing(10);
    QRadioButton *variableDisplayTypeNodeHeight =
      new QRadioButton(tr("Node Height"), variableDisplay);
    variableDisplayButtonGroup->addButton(variableDisplayTypeNodeHeight,
                                          ExtrudeStackedAttributes::NodeHeight);
    variableDisplayTypeLayout->addWidget(variableDisplayTypeNodeHeight);
    QRadioButton *variableDisplayTypeCellHeight =
      new QRadioButton(tr("Cell Height"), variableDisplay);
    variableDisplayButtonGroup->addButton(variableDisplayTypeCellHeight,
                                          ExtrudeStackedAttributes::CellHeight);
    variableDisplayTypeLayout->addWidget(variableDisplayTypeCellHeight);
    variableDisplayTypeIndex =
      new QRadioButton(tr("Index"), variableDisplay);
    variableDisplayButtonGroup->addButton(variableDisplayTypeIndex,
                                          ExtrudeStackedAttributes::VariableIndex);
    variableDisplayTypeLayout->addWidget(variableDisplayTypeIndex);
    connect(variableDisplayButtonGroup, SIGNAL(buttonClicked(int)),
            this, SLOT(variableDisplayChanged(int)));
    variableDisplayTypeValue =
      new QRadioButton(tr("Original Data"), variableDisplay);
    variableDisplayButtonGroup->addButton(variableDisplayTypeValue,
                                          ExtrudeStackedAttributes::OriginalData);
    variableDisplayTypeLayout->addWidget(variableDisplayTypeValue);
    connect(variableDisplayButtonGroup, SIGNAL(buttonClicked(int)),
            this, SLOT(variableDisplayChanged(int)));
    variableDisplayLayout->addWidget(variableDisplay, 1, 1);

#if defined(REPLICATE_EXTRUDE_OPERATOR)
    // Fixed to extrude
    fixedGroup = new QGroupBox(tr("Extrude by fixed length"), central);
    mainLayout->addWidget(fixedGroup, 9,0, 2,4);
    QGridLayout *fixedLayout = new QGridLayout(fixedGroup);

    // Fixed length or scaled variable.
    lengthLabel = new QLabel(tr("Length"), central);
    fixedLayout->addWidget(lengthLabel,0,0);
    length = new QLineEdit(central);
    connect(length, SIGNAL(returnPressed()),
            this, SLOT(lengthProcessText()));
    fixedLayout->addWidget(length, 0,1);

    // For breaking up the geometry.
    stepsLabel = new QLabel(tr("Number of steps"), central);
    fixedLayout->addWidget(stepsLabel,1,0);
    steps = new QLineEdit(central);
    connect(steps, SIGNAL(returnPressed()),
            this, SLOT(stepsProcessText()));
    fixedLayout->addWidget(steps, 1,1);
#endif

    // Preserve the cell numbers.
    preserveOriginalCellNumbers = new QCheckBox(tr("Preserve original cell numbers"), central);
    connect(preserveOriginalCellNumbers, SIGNAL(toggled(bool)),
            this, SLOT(preserveOriginalCellNumbersChanged(bool)));
    mainLayout->addWidget(preserveOriginalCellNumbers, 11,0);

    variableSelected(nullptr);
}


// ****************************************************************************
// Method: QvisExtrudeStackedWindow::UpdateWindow
//
// Purpose:
//   Updates the widgets in the window when the subject changes.
//
// Note:       Autogenerated by xml2window.
//
// Programmer: xml2window
// Creation:   omitted
//
// Modifications:
//   Kathleen Biagas, Mon Oct 16 14:05:19 PDT 2023
//   Qt6 support: sprintf->asprintf.
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::UpdateWindow(bool doAll)
{
    QString oldVariable = variableTree->currentItem() ?
        variableTree->currentItem()->text(1) : QString("");

    for(int i = 0; i < atts->NumAttributes(); ++i)
    {
        if(!doAll)
        {
            if(!atts->IsSelected(i))
            {
                continue;
            }
        }

        switch(i)
        {
          case ExtrudeStackedAttributes::ID_axis:
            axis->setText(DoublesToQString(atts->GetAxis(), 3));
            break;

          case ExtrudeStackedAttributes::ID_byVariable:
            variableGroup->blockSignals(true);
            variableGroup->setChecked(atts->GetByVariable());
            variableGroup->blockSignals(false);

#if defined(REPLICATE_EXTRUDE_OPERATOR)
            fixedGroup->setEnabled(atts->GetByVariable() == false);
#endif
            break;

          case ExtrudeStackedAttributes::ID_variableDisplay:
            variableDisplayButtonGroup->blockSignals(true);
            if(variableDisplayButtonGroup->button((int)atts->GetVariableDisplay()) != 0)
                variableDisplayButtonGroup->button((int)atts->GetVariableDisplay())->setChecked(true);
            variableDisplayButtonGroup->blockSignals(false);
            break;

          case ExtrudeStackedAttributes::ID_visualVariableNames:
          case ExtrudeStackedAttributes::ID_extentMinima:
          case ExtrudeStackedAttributes::ID_extentMaxima:
          case ExtrudeStackedAttributes::ID_extentScale:
            variableTree->blockSignals(true);

            // If the user changes the variable it is possible to have
            // the variable and the 'default' in the list. When that
            // happens delete the specific instance, keeping the more
            // general default.

            // std::cerr << std::endl;
            // std::cerr << "qvis default from UpdateOperatorAtts "
            //           << atts->GetDefaultVariable() << std::endl;
            // for (size_t ax=0; ax<atts->GetVisualVariableNames().size(); ax++)
            //   std::cerr << "qvis visible " << ax << "  "
            //             << atts->GetScalarVariableNames()[ax]  << "  "
            //             << atts->GetVisualVariableNames()[ax] << std::endl;

            // for (size_t ax=0; ax<atts->GetScalarVariableNames().size(); ax++)
            //   std::cerr << "qvis scalar " << ax << "  "
            //             << atts->GetScalarVariableNames()[ax]  << "  "
            //             << atts->GetVisualVariableNames()[ax] << std::endl;

            if(atts->GetVisualVariableNames().size() == 1)
            {
              variableDisplayTypeIndex->hide();
              variableDisplayTypeValue->show();

              if(atts->GetVariableDisplay() == ExtrudeStackedAttributes::VariableIndex )
              {
                atts->SetVariableDisplay(ExtrudeStackedAttributes::OriginalData );
                variableDisplayButtonGroup->button((int)atts->GetVariableDisplay())->setChecked(true);
                Apply();
              }
            }
            else
            {
              variableDisplayTypeIndex->show();
              variableDisplayTypeValue->hide();

              if(atts->GetVariableDisplay() == ExtrudeStackedAttributes::OriginalData )
              {
                atts->SetVariableDisplay(ExtrudeStackedAttributes::VariableIndex );
                variableDisplayButtonGroup->button((int)atts->GetVariableDisplay())->setChecked(true);
                Apply();
              }
            }

            // Note the check is done on BOTH the Qvis and avt side.
            if( atts->GetVisualVariableNames().size() > 1 )
            {
              int haveBoth = 0;

              for (size_t ax=0; ax<atts->GetVisualVariableNames().size(); ax++)
              {
                if(atts->GetVisualVariableNames()[ax] == "default" ||
                   atts->GetVisualVariableNames()[ax] == atts->GetDefaultVariable())
                  ++haveBoth;
              }

              if( haveBoth == 2 )
              {
                // atts->DeleteVariable("default", 0);
                atts->DeleteVariable(atts->GetDefaultVariable(), 0);

                Apply(true);
              }
            }

            variableTree->clear();

            if( atts->GetVisualVariableNames().size() )
            {
              size_t nVars = atts->GetVisualVariableNames().size();

              for (size_t ax=0; ax<nVars; ax++)
              {
                QString index, name, emin("min"), emax("max"), escale("1.0");
                index = IntToQString(nVars-1-ax);
                if (atts->GetVisualVariableNames().size() > ax)
                  name = (atts->GetVisualVariableNames()[ax]).c_str();
                else
                {
                  name.asprintf(" %02ld", ax);
                  name = tr("Variable") + name;
                }
                if (atts->GetExtentMinima()[ax] > -1e+37)
                  emin = DoubleToQString(atts->GetExtentMinima()[ax]);
                if (atts->GetExtentMaxima()[ax] < +1e+37)
                  emax = DoubleToQString(atts->GetExtentMaxima()[ax]);
                if (atts->GetExtentScale()[ax] != 0)
                  escale = DoubleToQString(atts->GetExtentScale()[ax]);

                QTreeWidgetItem *item = new QTreeWidgetItem(variableTree);
                item->setText(0, index);
                item->setText(1, name);
                item->setText(2, emin);
                item->setText(3, emax);
                item->setText(4, escale);
              }
            }
            else
            {
              addVariable("default");
            }

            variableTree->resizeColumnToContents(0);
            variableTree->resizeColumnToContents(1);
            variableTree->resizeColumnToContents(2);
            variableTree->resizeColumnToContents(3);
            variableTree->blockSignals(false);

            variableSelected(nullptr);
            break;

#if defined(REPLICATE_EXTRUDE_OPERATOR)
          case ExtrudeStackedAttributes::ID_length:
            length->setText(DoubleToQString(atts->GetLength()));
            break;
          case ExtrudeStackedAttributes::ID_steps:
            steps->setText(IntToQString(atts->GetSteps()));
            break;
#endif
          case ExtrudeStackedAttributes::ID_preserveOriginalCellNumbers:
            preserveOriginalCellNumbers->blockSignals(true);
            preserveOriginalCellNumbers->setChecked(atts->GetPreserveOriginalCellNumbers());
            preserveOriginalCellNumbers->blockSignals(false);
            break;
        }
    }

    // Re-select the previously selected item in case updating this window
    // regenerated the tree contents
    bool found = false;
    int nItems = variableTree->topLevelItemCount();

    for(int i=0; i<nItems && !found; ++i)
    {
        QTreeWidgetItem *item = variableTree->topLevelItem(i);
        if(item->text(1) == oldVariable)
        {
            variableTree->setCurrentItem(item);
            variableSelected(item);
            found = true;
        }
    }

    if (!found && nItems > 0)
    {
        QTreeWidgetItem *item = variableTree->topLevelItem(0);
        variableTree->setCurrentItem(item);
        variableSelected(item);
    }
}


// ****************************************************************************
// Method: QvisExtrudeStackedWindow::GetCurrentValues
//
// Purpose:
//   Gets values from certain widgets and stores them in the subject.
//
// Note:       Autogenerated by xml2window.
//
// Programmer: xml2window
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::GetCurrentValues(int which_widget)
{
    bool doAll = (which_widget == -1);

    // Do axis
    if(which_widget == ExtrudeStackedAttributes::ID_axis || doAll)
    {
        double val[3];
        if(LineEditGetDoubles(axis, val, 3))
            atts->SetAxis(val);
        else
        {
            ResettingError(tr("Extrusion axis"),
                DoublesToQString(atts->GetAxis(),3));
            atts->SetAxis(atts->GetAxis());
        }
    }

#if defined(REPLICATE_EXTRUDE_OPERATOR)
    // Do length
    if(which_widget == ExtrudeStackedAttributes::ID_length || doAll)
    {
        double val;
        if(LineEditGetDouble(length, val))
            atts->SetLength(val);
        else
        {
            ResettingError(tr("Length"),
                DoubleToQString(atts->GetLength()));
            atts->SetLength(atts->GetLength());
        }
    }

    // Do steps
    if(which_widget == ExtrudeStackedAttributes::ID_steps || doAll)
    {
        int val;
        if(LineEditGetInt(steps, val))
            atts->SetSteps(val);
        else
        {
            ResettingError(tr("Number of steps"),
                IntToQString(atts->GetSteps()));
            atts->SetSteps(atts->GetSteps());
        }
    }
#endif
}


//
// Qt Slot functions
//


void
QvisExtrudeStackedWindow::axisProcessText()
{
    GetCurrentValues(ExtrudeStackedAttributes::ID_axis);
    Apply();
}

void
QvisExtrudeStackedWindow::lengthProcessText()
{
    GetCurrentValues(ExtrudeStackedAttributes::ID_length);
    Apply();
}


void
QvisExtrudeStackedWindow::stepsProcessText()
{
    GetCurrentValues(ExtrudeStackedAttributes::ID_steps);
    Apply();
}


void
QvisExtrudeStackedWindow::preserveOriginalCellNumbersChanged(bool val)
{
    atts->SetPreserveOriginalCellNumbers(val);
    SetUpdate(false);
    Apply();
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::variableGroupChanged
//
//  Purpose:
//    extrude by variable(s)
//
//  Arguments:
//    val   boolean true - extrude by variable
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::variableGroupChanged(bool val)
{
    atts->SetByVariable(val);

#if defined(REPLICATE_EXTRUDE_OPERATOR)
    fixedGroup->setEnabled(atts->GetByVariable() == false);
#endif

    variableSelected(nullptr);

    SetUpdate(false);
    Apply();
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::addVariable
//
//  Purpose:
//    add a variable (i.e. variable) to the plot
//
//  Arguments:
//    variableToAdd   the name of the new variable to add
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::addVariable(const QString &variableToAdd)
{
    // The stacking order is "reversed" so insert at the beginning.
    atts->InsertVariable(variableToAdd.toStdString(), 0);

    // If the variable added is the default variable and one of the
    // variables entries is "default." Remove the "default" entry.
    if( variableToAdd.toStdString() == atts->GetDefaultVariable() )
    {
      int nItems = variableTree->topLevelItemCount();
      for(int i =0; i<nItems; ++i)
      {
        QTreeWidgetItem *item = variableTree->topLevelItem(i);

        if( item->text(1) == "default" )
        {
          variableTree->setCurrentItem(item);

          deleteVariable();

          break;
        }
      }
    }

    Apply();

    if( variableTree->topLevelItemCount() > 0 )
    {
      QTreeWidgetItem *item = variableTree->topLevelItem(0);
      variableTree->setCurrentItem(item);
      variableSelected(item);
    }
}

// ****************************************************************************
//  Method:  QvisParallelCoordinatesPlotWindow::deleteVariable
//
//  Purpose:
//    removes the currently selected variable (i.e. variable) from the plot
//
//  Arguments:
//    none
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::deleteVariable()
{
    if (variableTree->currentItem())
    {
        QString variable = variableTree->currentItem()->text(1);
        atts->DeleteVariable(variable.toStdString(), 0);

        // Make sure there is always one variable.
        if(atts->GetScalarVariableNames().size() == 0)
          atts->addVariable("default");

        variableSelected(nullptr);

        Apply();
    }
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::moveVariableUp
//
//  Purpose:
//    move the currently selected variable (i.e. variable) up in the list
//
//  Arguments:
//    none
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::moveVariableUp()
{
    // Get the index of the current item
    int index = GetSelectedVariableIndex();
    // Verify something is selected and it is not the first item
    // (can't move first variable up in list)
    if (index <= 0)
        return;

    // Must make a local copy
    stringVector variableNames = atts->GetScalarVariableNames();
    int nVars = (int) variableNames.size();

    // addVariable() will reorder the entries already in the list, so
    // just add all the changed ones in the new desired order.
    atts->addVariable(variableNames[index]);
    atts->addVariable(variableNames[index-1]);

    for (int i=index+1; i<nVars; i++)
        atts->addVariable(variableNames[i]);

    Apply();
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::moveVariableDown
//
//  Purpose:
//    move the currently selected variable (i.e. variable) down in the list
//
//  Arguments:
//    none
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::moveVariableDown()
{
    // Get the index of the current item
    int index = GetSelectedVariableIndex();
    // Verify something is selected.
    if( index < 0)
        return;

    // Must make a local copy
    stringVector variableNames = atts->GetScalarVariableNames();
    int nVars = (int) variableNames.size();

    // Can't move last variable down in list
    if (index >= nVars-1)
        return;

    // addVariable() will reorder the entries already in the list, so
    // just add all the changed ones in the new desired order.
    atts->addVariable(variableNames[index+1]);
    atts->addVariable(variableNames[index]);
    for (int i=index+2; i<nVars; i++)
        atts->addVariable(variableNames[i]);

    // Done; apply changes
    Apply();
}


// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::variableSelected
//
//  Purpose:
//    Executed when a variable is seected in the list.  This function
//    only sets the enabled states of the variable modifier buttons.
//
//  Arguments:
//    variable       the index in the list box
//
//  Programmer:  Jeremy Meredith
//  Creation:    March 16, 2007
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::variableSelected(QTreeWidgetItem*)
{
    size_t nItems;
    if( atts->GetByVariable() )
      nItems = atts->GetScalarVariableNames().size();
    else
      nItems = 0;

    variableDisplayGroup->setEnabled(nItems > 0);

    QTreeWidgetItem *ci = variableTree->currentItem();
    variableDeleteButton->setEnabled(nItems > 0 && ci != NULL);
    variableUpButton->setEnabled(nItems > 0 &&
                                 ci != variableTree->topLevelItem(0));
    variableDownButton->setEnabled(nItems > 0 &&
                                   ci != variableTree->topLevelItem(nItems-1));

    variableMinValLabel->setEnabled(nItems > 0);
    variableMinVal->setEnabled(nItems > 0);
    variableMaxValLabel->setEnabled(nItems > 0);
    variableMaxVal->setEnabled(nItems > 0);
    variableScaleValLabel->setEnabled(nItems > 0);
    variableScaleVal->setEnabled(nItems > 0);

    variableResetExtentsButton->setEnabled(nItems > 0);

    if (ci)
    {
        variableMinVal  ->setText(ci->text(2));
        variableMaxVal  ->setText(ci->text(3));
        variableScaleVal->setText(ci->text(4));
    }
    else
    {
        variableMinVal  ->setText("");
        variableMaxVal  ->setText("");
        variableScaleVal->setText("");
    }
}

// ****************************************************************************
// Method:  QvisExtrudeStackedWindow::variableValueProcessText
//
// Purpose:
//   When return is pressed in the min, max, or scale variable value
//   text field, this method is called.  We don't have to actually
//   populate the value in the local atts since that gets updated as
//   they type.
//
// Arguments:
//   none
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************
void
QvisExtrudeStackedWindow::variableValueProcessText()
{
    atts->Notify();
    Apply();
}

// ****************************************************************************
// Method:  QvisExtrudeStackedWindow::variableMinValueChanged
//
// Purpose:
//   Called when the user types in the variable min value text field.
//   We update the local copy here as they type so that if they
//   click on another variable before hitting apply or enter, it retains
//   the values they entered.  (Which means they can update all axes
//   before hitting apply -- or return for auto-update.)
//
// Arguments:
//   val        the current contents of the field
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************
void
QvisExtrudeStackedWindow::variableMinValueChanged(const QString &val)
{
    int index = GetSelectedVariableIndex();
    if (index < 0)
        return;

    bool ok = false;
    double v = val.toDouble(&ok);
    if (val == "min")
    {
        ok = true;
        v = -1e+37;
    }
    else if (val == "max")
    {
        ok = true;
        v = +1e+37;
    }

    if (!ok)
        return;

    atts->GetExtentMinima()[index] = v;
    atts->SelectExtentMinima();
    variableTree->topLevelItem(index)->setText(2, val);
}

// ****************************************************************************
// Method:  QvisExtrudeStackedWindow::variableMaxValueChanged
//
// Purpose:
//   Called when the user types in the variable max value text field.
//   We update the local copy here as they type so that if they
//   click on another variable before hitting apply or enter, it retains
//   the values they entered.  (Which means they can update all axes
//   before hitting apply -- or return for auto-update.)
//
// Arguments:
//   val        the current contents of the field
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************
void
QvisExtrudeStackedWindow::variableMaxValueChanged(const QString &val)
{
    int index = GetSelectedVariableIndex();
    if (index < 0)
        return;

    bool ok = false;
    double v = val.toDouble(&ok);
    if (val == "min")
    {
        ok = true;
        v = -1e+37;
    }
    else if (val == "max")
    {
        ok = true;
        v = +1e+37;
    }

    if (!ok)
        return;

    atts->GetExtentMaxima()[index] = v;
    atts->SelectExtentMaxima();
    variableTree->topLevelItem(index)->setText(3, val);
}

// ****************************************************************************
// Method:  QvisExtrudeStackedWindow::variableScaleValueChanged
//
// Purpose:
//   Called when the user types in the variable scale value text field.
//   We update the local copy here as they type so that if they
//   click on another variable before hitting apply or enter, it retains
//   the values they entered.  (Which means they can update all axes
//   before hitting apply -- or return for auto-update.)
//
// Arguments:
//   val        the current contents of the field
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************
void
QvisExtrudeStackedWindow::variableScaleValueChanged(const QString &val)
{
    int index = GetSelectedVariableIndex();
    if (index < 0)
        return;

    bool ok = false;
    double v = val.toDouble(&ok);
    if (val == "0" || v == 0)
    {
        return;
    }

    atts->GetExtentScale()[index] = v;
    atts->SelectExtentScale();
    variableTree->topLevelItem(index)->setText(4, val);
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedWindow::resetVariableExtents
//
//  Purpose:
//    Resets all extents to the full min/max range and the scale to 1.
//
//  Arguments:
//    none
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::resetVariableExtents()
{
    for (size_t i=0; i<atts->GetExtentMinima().size(); i++)
    {
        atts->GetExtentMinima()[i] = -1e+37;
    }
    for (size_t i=0; i<atts->GetExtentMaxima().size(); i++)
    {
        atts->GetExtentMaxima()[i] = +1e+37;
    }

    for (size_t i=0; i<atts->GetExtentScale().size(); i++)
    {
        atts->GetExtentScale()[i] = 1.0;
    }

    atts->SelectExtentMinima();
    atts->SelectExtentMaxima();
    atts->SelectExtentScale();

    Apply();
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedPlotWindow::GetSelectedVariableIndex
//
//  Purpose:
//    Helper that obtains the index of the currently selected variable, or
//    -1 if no variable is selected.
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

int
QvisExtrudeStackedWindow::GetSelectedVariableIndex()
{
    int nItems = variableTree->topLevelItemCount();
    for(int i =0; i<nItems; ++i)
    {
        if(variableTree->currentItem() == variableTree->topLevelItem(i))
            return i;
    }
    return -1;
}

// ****************************************************************************
//  Method:  QvisExtrudeStackedPlotWindow::variableDisplayChanged
//
//  Purpose:
//    Helper that obtains the index of the currently selected variable, or
//    -1 if no variable is selected.
//
//
//  Programmer: Allen Sanderson
//  Creation:   August 31, 2023
//
// ****************************************************************************

void
QvisExtrudeStackedWindow::variableDisplayChanged(int val)
{
    if(val != atts->GetVariableDisplay())
    {
        atts->SetVariableDisplay(ExtrudeStackedAttributes::VariableDisplayType(val));
        Apply();
    }
}
